{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Modeling and Simulation in Python\n",
    "\n",
    "Chapter 6\n",
    "\n",
    "Copyright 2017 Allen Downey\n",
    "\n",
    "License: [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Configure Jupyter so figures appear in the notebook\n",
    "%matplotlib inline\n",
    "\n",
    "# Configure Jupyter to display the assigned value after an assignment\n",
    "%config InteractiveShell.ast_node_interactivity='last_expr_or_assign'\n",
    "\n",
    "# import functions from the modsim.py module\n",
    "from modsim import *\n",
    "\n",
    "from pandas import read_html"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Code from the previous chapter\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "filename = 'data/World_population_estimates.html'\n",
    "tables = read_html(filename, header=0, index_col=0, decimal='M')\n",
    "table2 = tables[2]\n",
    "table2.columns = ['census', 'prb', 'un', 'maddison', \n",
    "                  'hyde', 'tanton', 'biraben', 'mj', \n",
    "                  'thomlinson', 'durand', 'clark']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "un = table2.un / 1e9\n",
    "un.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "census = table2.census / 1e9\n",
    "census.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "t_0 = get_first_label(census)\n",
    "t_end = get_last_label(census)\n",
    "elapsed_time = t_end - t_0\n",
    "\n",
    "p_0 = get_first_value(census)\n",
    "p_end = get_last_value(census)\n",
    "total_growth = p_end - p_0\n",
    "\n",
    "annual_growth = total_growth / elapsed_time"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### System objects"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can rewrite the code from the previous chapter using system objects."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "system = System(t_0=t_0, \n",
    "                t_end=t_end,\n",
    "                p_0=p_0,\n",
    "                annual_growth=annual_growth)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we can encapsulate the code that runs the model in a function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_simulation1(system):\n",
    "    \"\"\"Runs the constant growth model.\n",
    "    \n",
    "    system: System object\n",
    "    \n",
    "    returns: TimeSeries\n",
    "    \"\"\"\n",
    "    results = TimeSeries()\n",
    "    results[system.t_0] = system.p_0\n",
    "    \n",
    "    for t in linrange(system.t_0, system.t_end):\n",
    "        results[t+1] = results[t] + system.annual_growth\n",
    "    \n",
    "    return results"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also encapsulate the code that plots the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_results(census, un, timeseries, title):\n",
    "    \"\"\"Plot the estimates and the model.\n",
    "    \n",
    "    census: TimeSeries of population estimates\n",
    "    un: TimeSeries of population estimates\n",
    "    timeseries: TimeSeries of simulation results\n",
    "    title: string\n",
    "    \"\"\"\n",
    "    plot(census, ':', label='US Census')\n",
    "    plot(un, '--', label='UN DESA')\n",
    "    plot(timeseries, color='gray', label='model')\n",
    "    \n",
    "    decorate(xlabel='Year', \n",
    "             ylabel='World population (billion)',\n",
    "             title=title)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's how we run it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "results = run_simulation1(system)\n",
    "plot_results(census, un, results, 'Constant growth model')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Proportional growth"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's a more realistic model where the number of births and deaths is proportional to the current population."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_simulation2(system):\n",
    "    \"\"\"Run a model with proportional birth and death.\n",
    "    \n",
    "    system: System object\n",
    "    \n",
    "    returns: TimeSeries\n",
    "    \"\"\"\n",
    "    results = TimeSeries()\n",
    "    results[system.t_0] = system.p_0\n",
    "    \n",
    "    for t in linrange(system.t_0, system.t_end):\n",
    "        births = system.birth_rate * results[t]\n",
    "        deaths = system.death_rate * results[t]\n",
    "        results[t+1] = results[t] + births - deaths\n",
    "        \n",
    "    return results"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "I picked a death rate that seemed reasonable and then adjusted the birth rate to fit the data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "system.death_rate = 0.01\n",
    "system.birth_rate = 0.027"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's what it looks like."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "results = run_simulation2(system)\n",
    "plot_results(census, un, results, 'Proportional model')\n",
    "savefig('figs/chap06-fig01.pdf')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The model fits the data pretty well for the first 20 years, but not so well after that."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Factoring out the update function"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`run_simulation1` and `run_simulation2` are nearly identical except the body of the loop.  So we can factor that part out into a function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def update_func1(pop, t, system):\n",
    "    \"\"\"Compute the population next year.\n",
    "    \n",
    "    pop: current population\n",
    "    t: current year\n",
    "    system: system object containing parameters of the model\n",
    "    \n",
    "    returns: population next year\n",
    "    \"\"\"\n",
    "    births = system.birth_rate * pop\n",
    "    deaths = system.death_rate * pop\n",
    "    return pop + births - deaths"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The name `update_func` refers to a function object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "update_func1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Which we can confirm by checking its type."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "type(update_func1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`run_simulation` takes the update function as a parameter and calls it just like any other function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_simulation(system, update_func):\n",
    "    \"\"\"Simulate the system using any update function.\n",
    "    \n",
    "    system: System object\n",
    "    update_func: function that computes the population next year\n",
    "    \n",
    "    returns: TimeSeries\n",
    "    \"\"\"\n",
    "    results = TimeSeries()\n",
    "    results[system.t_0] = system.p_0\n",
    "    \n",
    "    for t in linrange(system.t_0, system.t_end):\n",
    "        results[t+1] = update_func(results[t], t, system)\n",
    "        \n",
    "    return results"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's how we use it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "t_0 = get_first_label(census)\n",
    "t_end = get_last_label(census)\n",
    "p_0 = census[t_0]\n",
    "\n",
    "system = System(t_0=t_0, \n",
    "                t_end=t_end,\n",
    "                p_0=p_0,\n",
    "                birth_rate=0.027,\n",
    "                death_rate=0.01)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "results = run_simulation(system, update_func1)\n",
    "plot_results(census, un, results, 'Proportional model, factored')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Remember not to put parentheses after `update_func1`.  What happens if you try?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** When you run `run_simulation`, it runs `update_func1` once for each year between `t_0` and `t_end`.  To see that for yourself, add a print statement at the beginning of `update_func1` that prints the values of `t` and `pop`, then run `run_simulation` again."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Combining birth and death"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since births and deaths get added up, we don't have to compute them separately.  We can combine the birth and death rates into a single net growth rate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "def update_func2(pop, t, system):\n",
    "    \"\"\"Compute the population next year.\n",
    "    \n",
    "    pop: current population\n",
    "    t: current year\n",
    "    system: system object containing parameters of the model\n",
    "    \n",
    "    returns: population next year\n",
    "    \"\"\"\n",
    "    net_growth = system.alpha  * pop\n",
    "    return pop + net_growth"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's how it works:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "system.alpha = system.birth_rate - system.death_rate\n",
    "\n",
    "results = run_simulation(system, update_func2)\n",
    "plot_results(census, un, results, 'Proportional model, combined birth and death')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercises\n",
    "\n",
    "**Exercise:** Maybe the reason the proportional model doesn't work very well is that the growth rate, `alpha`, is changing over time.  So let's try a model with different growth rates before and after 1980 (as an arbitrary choice).\n",
    "\n",
    "Write an update function that takes `pop`, `t`, and `system` as parameters.  The system object, `system`, should contain two parameters: the growth rate before 1980, `alpha1`, and the growth rate after 1980, `alpha2`.  It should use `t` to determine which growth rate to use.  Note: Don't forget the `return` statement.\n",
    "\n",
    "Test your function by calling it directly, then pass it to `run_simulation`.  Plot the results.  Adjust the parameters `alpha1` and `alpha2` to fit the data as well as you can.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
